// Copyright  2004-2008 Backplane Ltd.
//
// This file is part of Ubiquity formsPlayer
//
// Ubiquity formsPlayer is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3 of the License, or (at your
// option) any later version.
//
// Ubiquity formsPlayer is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License
// along with Ubiquity formsPlayer If not, see <http://www.gnu.org/licenses/>.
//	
//
//   Purpose: implementation of CImplBind.
//   $Id$

#include "stdafx.h"
#include "ImplBind.h"
#include "boundNode.h"
#include "model.h"
#include "vertex.h"
#include "xformsApp.h"
#include "nodebinding.h"
extern CXFormsApp theApp;
wchar_t * g_wsConstraintWrapper = L"boolean(%s)";

// rebuilds m_instVertices
void CImplBind::rebuildTargetNodeset(IXMLDOMNode * pContext,CIdResolver * pResolver)
{
	m_instVertices.RemoveAll();
	if(m_sRef)
	{
		if(!pContext && m_pModel)
		{
			m_pModel->GetDefaultEvaluationContext(&pContext);
		}
		else
		{
			//Increment the reference count so that we can just call release at the bottom
			pContext->AddRef();
		}

		CVertex::EType type;
		type=CVertex::EType::Instance;
		m_pModel->addVertex(pContext,m_sRef,false,type,NULL,&m_instVertices,pResolver);	
		::safeRelease(pContext);
	}
}

//TODO: rearrange this function.  It was lifted wholesale from 
//	addToGraph
HRESULT CImplBind::fetchType()
{
	HRESULT hr = S_FALSE;
	//We need do this only once for each bind, the type object
	//	is cached after the first run, so subsequent rebuilds
	//	don't have to run through the string parsing etc.
		
	if(m_pType)
	{
		//if m_pType has already been resolved, do nothing.
		hr = S_OK;
	}
	//if there is a type attribute, fetch the type from the schemacache.
	else if(m_sType && m_sType.m_str[0])
	{
		CComBSTR sNSURI("");
		CStringW wsXSIType = m_sType;
		if(wsXSIType && !wsXSIType.IsEmpty())
		{
			//find the URI associated with the type
			long lColonPos = wsXSIType.Find(':');
			bool bLookupFailed = false;
			if(lColonPos > 0) //NB: > not !=
			{
				CComPtr<IXPathNSResolver> pResolver;
				m_pXForms->GetNSResolver(&pResolver);
				if(pResolver) {
					BSTR s = wsXSIType.Left(lColonPos).AllocSysString();
					pResolver->lookupNamespaceURI(s,&sNSURI);
					::SysFreeString(s);
				} if(!sNSURI)
				{
					bLookupFailed = true;
					CString sErr;
					sErr.Format("Undeclared namespace prefix resolving type: %S",wsXSIType);
					theApp.reportError(sErr);
					hr = E_FAIL;
				}
					
				wsXSIType.Delete(0,lColonPos+1);
			}
			//Fetch the type from the schema.
			//TODO: this probably won't work if there is 
			//	no targetNamespace in the schema.
			if(!bLookupFailed) {
				CComQIPtr<IXMLDOMSchemaCollection2> pSchemaCache2;
				pSchemaCache2 = m_pModel->m_pSchemaCache;
				if(pSchemaCache2) {
					CComPtr<ISchema> pSchema;
					pSchemaCache2->getSchema(sNSURI,&pSchema);
					if(pSchema) {
						CComPtr<ISchemaItemCollection> pTypes;
						pSchema->get_types(&pTypes);
						if(pTypes) {
							CComPtr<ISchemaItem> pItem;
							BSTR s = wsXSIType.AllocSysString();
							pTypes->itemByQName(s,sNSURI,&pItem);
							::SysFreeString(s);
							if(pItem)
								hr = pItem->QueryInterface(IID_ISchemaType,(void**)&m_pType);
							else {
								CString sErr;
								sErr.Format("Undeclared type: %S",wsXSIType);
								theApp.reportError(sErr);
								hr = E_FAIL;
							}
						}
					}
				}
			}
		}
	}
	return hr;
}

bool CImplBind::RemoveControl(CImplNodeBinding * pControl)
{
	bool bReturn = false;
	POSITION pos = m_controls.Find(pControl);
	if(pos)
	{
		m_controls.RemoveAt(pos);
		bReturn = true;
	}
	return bReturn;
}
CImplBind::CImplBind(CImplModel * pMod)
	:
	m_sType(0)
	,m_sReadOnly(0)
	, m_sRequired(0)
	, m_sRelevant(0)
	, m_sConstraint(0)
	, m_sCalculate(0)

	//even ref is an optional attribute, I can therefore only assume
	//	that any model-item-properties specified by a refless bind
	//	refer to the evaluation context, if top-level, this will be 
	//	equivalent to "/*"
	, m_sRef(".")
	,m_pType(0)
{
		setModel(pMod);
		m_pXForms = pMod->m_pXForms;
}

void CImplBind::AddBind(CImplBind * pBind)
{
	m_childBinds.AddTail(pBind);
	//We don't want child binds to be members
	//	of the model, as they behave slightly differently (evaluation context)
//	m_pModel->AddBind(pBind);
	//TODO: throw error for duplicate id.
	if(pBind->m_sId)
		m_pXForms->m_Binds.SetAt(CString(pBind->m_sId),pBind);

}

void CImplBind::AddControl(CImplNodeBinding * pControl)
{
	m_controls.AddTail(pControl);
	pControl->ClearBindings();
	if(m_instVertices.GetSize() > 0)
	{
		CVertex * pAddToVertex = m_instVertices.GetAt(0);
		pControl->m_vertices.Add(pAddToVertex);
		pAddToVertex->m_pInstanceNode->addTarget(pControl);
	}

	if(!pControl->m_bApplyFirstNodeRule)
	{
		long lSize = m_instVertices.GetSize();
		//loop through from one if the control is a multiple node binding,
		//	0 has already been added.
		for(long l = 1 ; l < lSize ; ++l)
		{
			pControl->m_vertices.Add(m_instVertices.GetAt(l));
			m_instVertices.GetAt(l)->m_pInstanceNode->addTarget(pControl);
		}
	}
}

CImplBind::~CImplBind()
{
	try
	{
		m_instVertices.RemoveAll();
		while(!m_childBinds.IsEmpty())
		{
			try
			{
				delete m_childBinds.RemoveHead();
			}
			catch(...)
			{
				ASSERT(0);
			}
		}
		::safeRelease(m_pType);
	}
	catch(...)
	{
		ASSERT(0);
	}
};


void CImplBind::AddToGraph(CVertexList *pGraph, IXMLDOMNode * pContext)
{
	//pContext defaults to NULL, it is only defined for binds which are children of binds.

	//create bindings for each of the properties.
	CIdResolver * pResolver = GetIdResolver();
	rebuildTargetNodeset(pContext,pResolver);

	long lUBound = m_instVertices.GetUpperBound();
	if(lUBound > -1)
	{
		fetchType();
		//
		//	http://www.w3.org/TR/2002/CR-xforms-20021112/slice7.html#expr-eval
		//	Rule 4  -The context node for computed expressions (occurring on element bind) 
		//				is the first node of the node-set returned from the binding expression  
		//				in the nodeset attribute on the same element.
		//	This rule is wrong, it should be the affected node
	
		//TODO: Investigate hoisting the ifs out.
		//	If a bind's nodeset has n nodes in it, 	then at present this loop involves
		//		n calls to GetAt, plus
		//		5n checks for the emptiness of the string members
		//	Hoisting, will, at worst case (bind has all MIP attributes + calculate set)
		//		be 5n calls to GetAt + 5 checks for emptiness
		//	If there is just the one MIP set, then it will be 
		//		n calls to GetAt + 5 checks for emptiness.
		//
		//	The question is, how crap is CArray::GetAt?  
		//	Is it preferable to do 10n checks for ptr !=0, rather than all those GetAts?
		//	We should switch to using an stl class (list/array/whatever)
		//	
		//	Hoisting will also allow make this function a bit smaller.
		for(; lUBound >=0; --lUBound)
		{
			CBoundNode * pBoundNode =  m_instVertices.GetAt(lUBound)->m_pInstanceNode;
			if(pBoundNode)
			{
				CComPtr<IXMLDOMNode> pNode;
				pBoundNode->getNode(&pNode);
				if(m_sReadOnly && m_sReadOnly.m_str[0])
				{
					pGraph->setVertex
					(
						m_sReadOnly,
						pNode,
						CVertex::EType::Readonly,
						NULL,
						pResolver,
						m_pModel
					);
				}
				if(m_sCalculate && m_sCalculate.m_str[0])
				{	
					pGraph->setVertex
					(
						m_sCalculate,
						pNode,
						CVertex::EType::Calculate,
						NULL,
						pResolver,
						m_pModel
					);
				}
				if(m_sConstraint && m_sConstraint.m_str[0])
				{
					CStringW sWrappedConstraint;
					sWrappedConstraint.Format(g_wsConstraintWrapper,m_sConstraint);
					CComBSTR bsWrappedConstraint;
					bsWrappedConstraint.Attach(sWrappedConstraint.AllocSysString());
					pGraph->setVertex
					(
						bsWrappedConstraint,
						pNode,
						CVertex::EType::Constraint,
						NULL,
						pResolver,
						m_pModel
					);

				}
				if(m_sRelevant && m_sRelevant.m_str[0])
				{
					pGraph->setVertex
					(
						m_sRelevant,
						pNode,
						CVertex::EType::Relevant,
						NULL,
						pResolver,
						m_pModel
					);
				}
				if(m_sRequired && m_sRequired.m_str[0])
				{
					pGraph->setVertex
					(
						m_sRequired,
						pNode,
						CVertex::EType::Required,
						NULL,
						pResolver,
						m_pModel
					);
				}
				if(m_pType)
				{
					pBoundNode->putExternalType(m_pType);
				}
				//add child binds using the outer evaluation context.
				POSITION pos = m_childBinds.GetHeadPosition();
				while(pos)
				{
					CImplBind * pChildBind = m_childBinds.GetNext(pos);
					pChildBind->AddToGraph(pGraph,pNode);
				}
			}

			
		}

		//refresh the vertices in any controls that point to this bind.
		POSITION pos = m_controls.GetHeadPosition();

		while(pos)
		{
			CImplNodeBinding * pControl = m_controls.GetNext(pos);
			pControl->m_vertices.RemoveAll();
			//pControl->ClearBindings();
			long lOldNodesSize = pControl->m_OldNodes.GetSize();
	
			CVertex * pVx = m_instVertices.GetAt(0);

			pControl->m_vertices.Add(pVx);
			bool bReceiveStateChangeEvents = true;
			if(0 < lOldNodesSize)
			{
				CBoundNode * pOldNode = pControl->m_OldNodes.GetAt(0);
				if(pOldNode == pVx->m_pInstanceNode)
					bReceiveStateChangeEvents = false;
			}
			pVx->m_pInstanceNode->addTarget(pControl,!bReceiveStateChangeEvents);

			if(!pControl->m_bApplyFirstNodeRule)
			{
				long lSize = m_instVertices.GetSize();
				//loop through from one if the control is a multiple node binding,
				//	0 has already been added.
				for(long l = 1 ; l < lSize ; ++l)
				{
					//If this node was present in the last binding (pre refresh)
					//	don't receive  state change events
					CVertex * pVx = m_instVertices.GetAt(l);
					bool bReceiveStateChangeEvents = true;
					if(l < lOldNodesSize && pControl->m_OldNodes.GetAt(l) == pVx->m_pInstanceNode)
						bReceiveStateChangeEvents = false;
					pControl->m_vertices.Add(pVx);
					pVx->m_pInstanceNode->addTarget(pControl,!bReceiveStateChangeEvents);

				}
			}
			pControl->ConfirmBindings();
		}
	}


}


HRESULT CImplBind::init()
{
	
	return S_OK;
}

void CImplBind::notifyDelete()
{

}


HRESULT CImplBind::GetNodeAt(const long lIndex, IXMLDOMNode **ppRetval)
{
	HRESULT hr = S_FALSE;
	long lSize = m_instVertices.GetSize();
	if(lSize > lIndex)
	{
		CVertex * pVertex = m_instVertices.GetAt(lIndex);	
		if(pVertex)
		{
			hr = pVertex->m_pInstanceNode->getNode(ppRetval);
		}
	}
	return hr;
}

long CImplBind::GetNodesetLength()
{
	return m_instVertices.GetSize();
}